﻿Module mMobs

    Public Structure MobStats

        Dim STR_NAME As String
        Dim INT2_SHAPE As Int16
        Dim INT2_LEVEL As Int16
        Dim BOOL_ISELITE As Boolean
        Dim BOOL_ISBOSS As Boolean
        Dim BOOL_ISTAMABLE As Boolean
        Dim BOOL_ISUNDEAD As Boolean
        Dim INT2_AI As Int16
        Dim Stats As StatsSet
        Dim HealPoints As Int16
        Dim HealSpeed As Long
        Dim AvatarImg As Int16

        Dim INT8_EXP As Long

        Dim Speed As Int16
        Dim Hit As Int16
        Dim WalkSpeed As Long
        Dim WalkStep As Boolean
        Dim AttackSpeed As Long

    End Structure

    Public Structure MirMapMob

        Dim map As MirMap
        Dim stats As MobStats

        Dim ID_MOB As Int64
        Dim ServerMobID As Int64
        Dim ServerSpawnID As Int64
        Dim Pos As MobPositionInfo
        Dim IsAlive As Boolean

        Dim VitalPoints As Vitals

        Dim AIState As Integer
        Dim AITimer As Long
        Dim AIMoveTime As Integer
        Dim DoHitNextTick As Boolean
        Dim AIHitTimer As Integer
        Dim AITargetRage As Integer
        Dim AITargetType As TargetType
        Dim AITargetIdx As Integer
        Dim AIHealTimer As Long

    End Structure

    Public Enum TargetType As Byte
        Player = 0
    End Enum

    Public colMobDB As Collection

    Public Sub LoadMobDB()

        Output.AddLine("\B0\F5SV>\F0\B8 Loading Mob DB...")

        colMobDB = New Collection

        Dim arr As ArrayList = DB.GameDB.ExecuteQuery("SELECT * FROM TBL_MON;")
        Dim struct As MobStats

        If arr.Count > 0 Then

            For Each tmp As Collection In arr

                struct = New MobStats
                struct.BOOL_ISBOSS = tmp("BOOL_ISBOSS")
                struct.BOOL_ISELITE = tmp("BOOL_ISELITE")
                struct.BOOL_ISTAMABLE = tmp("BOOL_ISTAMABLE")
                struct.BOOL_ISUNDEAD = tmp("BOOL_ISUNDEAD")
                struct.INT2_AI = tmp("INT2_AI")
                struct.Stats.INT2_ELEMAC_DARK = tmp("INT2_ELEMAC_DARK")
                struct.Stats.INT2_ELEMAC_FIRE = tmp("INT2_ELEMAC_FIRE")
                struct.Stats.INT2_ELEMAC_HOLY = tmp("INT2_ELEMAC_HOLY")
                struct.Stats.INT2_ELEMAC_ICE = tmp("INT2_ELEMAC_ICE")
                struct.Stats.INT2_ELEMAC_LIGHT = tmp("INT2_ELEMAC_LIGHT")
                struct.Stats.INT2_ELEMAC_PHANTOM = tmp("INT2_ELEMAC_PHANTOM")
                struct.Stats.INT2_ELEMAC_WIND = tmp("INT2_ELEMAC_WIND")
                struct.Stats.INT2_ELEMC_DARK = tmp("INT2_ELEMC_DARK")
                struct.Stats.INT2_ELEMC_FIRE = tmp("INT2_ELEMC_FIRE")
                struct.Stats.INT2_ELEMC_HOLY = tmp("INT2_ELEMC_HOLY")
                struct.Stats.INT2_ELEMC_ICE = tmp("INT2_ELEMC_ICE")
                struct.Stats.INT2_ELEMC_LIGHT = tmp("INT2_ELEMC_LIGHT")
                struct.Stats.INT2_ELEMC_PHANTOM = tmp("INT2_ELEMC_PHANTOM")
                struct.Stats.INT2_ELEMC_WIND = tmp("INT2_ELEMC_WIND")
                struct.INT2_LEVEL = tmp("INT2_LEVEL")
                struct.Stats.INT2_MAX_AC = tmp("INT2_MAX_AC")
                struct.Stats.INT2_MAX_MAC = tmp("INT2_MAX_MAC")
                struct.Stats.INT2_MAX_DC = tmp("INT2_MAX_DC")
                struct.Stats.INT2_MAX_MC = tmp("INT2_MAX_MC")
                struct.Stats.INT2_MAX_MP = tmp("INT2_MAX_MP")
                struct.Stats.INT2_MAX_HP = tmp("INT2_MAX_HP")
                struct.Stats.INT2_MIN_AC = tmp("INT2_MIN_AC")
                struct.Stats.INT2_MIN_MAC = tmp("INT2_MIN_MAC")
                struct.Stats.INT2_MIN_DC = tmp("INT2_MIN_DC")
                struct.Stats.INT2_MIN_MC = tmp("INT2_MIN_MC")
                struct.INT2_SHAPE = tmp("INT2_SHAPE")
                struct.STR_NAME = tmp("STR_NAME")
                struct.HealPoints = tmp("INT2_HEAL_POINTS")
                struct.HealSpeed = tmp("INT8_HEALSPEED")

                struct.INT8_EXP = tmp("INT8_EXP")

                struct.Speed = tmp("INT2_SPEED")
                struct.Hit = tmp("INT2_HIT")
                struct.WalkSpeed = tmp("INT8_WALKSPEED")
                struct.WalkStep = tmp("BOOL_WALKSTEP")
                struct.AttackSpeed = tmp("INT8_ATTACKSPEED")


                struct.AvatarImg = tmp("INT2_AVATAR")
                'TODO: add mob exp
                'struct.INT_EXP = tmp("INT4_EXP")

                colMobDB.Add(struct, "MON" & tmp("ID_MON"))

            Next

        End If

    End Sub

    Public Function GenerateMobSpawnID() As Int64

        Static tmp As Int64
        tmp += 1
        Return tmp

    End Function

    Private tmpmobid As Int64
    Public Function GenerateMobID() As Int64

        tmpmobid += 1
        Return tmpmobid

    End Function

    Public MobAIs(1) As MobAI

    Public Sub LoadAI()

        MobAIs(0) = New MobAI1
        MobAIs(1) = New MobAI2

    End Sub

End Module

Friend Class cMobList

    Friend Structure MobSpawn
        Dim SpawnID As Int64
        Dim Stats As MobStats
        Dim ID_MON As Int64
        Dim INT2_X As Int16
        Dim INT2_Y As Int16
        Dim INT2_RANGEX As Int16
        Dim INT2_RANGEY As Int16
        Dim INT2_COUNT As Int16
        Dim map As MirMap
    End Structure

    Dim mMap As String
    Dim objmap As MirMap
    Dim mSpawns As Collection
    Friend mColMobs As List(Of MirMapMob)

    Public Sub New(ByVal map As String, ByRef mapobj As MirMap)

        mMap = map
        objmap = mapobj
        mSpawns = New Collection
        mColMobs = New List(Of MirMapMob)

    End Sub

    Public Sub SpawnMob(ByVal ID_MON As Long, ByVal X As Int32, ByVal y As Int32)

        Dim tmp As MirMapMob = New MirMapMob

        tmp.ServerMobID = GenerateMobID()
        tmp.ID_MOB = ID_MON
        tmp.stats = mMobs.colMobDB("MON" & ID_MON)
        tmp.IsAlive = True

        Dim tries As Integer
        Do
            tmp.Pos.INT2_X = X + (Int(Rnd() * 4) - 2)
            tmp.Pos.INT2_Y = y + (Int(Rnd() * 4) - 2)
            tries += 1
        Loop Until objmap.isPassable(tmp.Pos.INT2_X, tmp.Pos.INT2_Y) Or tries = 8

        tmp.Pos.BYTE_DIRECTION = Direction.SW

        tmp.Pos.INT8_SPEED = 2500000

        tmp.map = objmap

        tmp.VitalPoints.HP = CInt(tmp.stats.Stats.INT2_MAX_HP)
        tmp.VitalPoints.MP = CInt(tmp.stats.Stats.INT2_MAX_MP)
        tmp.VitalPoints.MAXHP = tmp.VitalPoints.HP
        tmp.VitalPoints.MAXMP = tmp.VitalPoints.MP

        SyncLock objmap.objMapLock
            mColMobs.Add(tmp)
        End SyncLock

        MobAddToPlayers(tmp)

    End Sub

    Public Sub LoadSpawns()

        Dim sSql As String = "SELECT * FROM TBL_MONSPAWN WHERE STR_MAPNAME='" & mMap & "';"
        Dim arr As ArrayList = DB.GameDB.ExecuteQuery(sSql)

        If arr.Count > 0 Then

            Dim tmp As MobSpawn

            For Each spawn As Collection In arr

                tmp = New MobSpawn
                tmp.ID_MON = spawn("ID_MON")
                tmp.INT2_COUNT = spawn("INT2_COUNT")
                tmp.INT2_RANGEX = spawn("INT2_RANGEX")
                tmp.INT2_RANGEY = spawn("INT2_RANGEY")
                tmp.INT2_X = spawn("INT2_X")
                tmp.INT2_Y = spawn("INT2_Y")
                tmp.SpawnID = GenerateMobSpawnID()
                tmp.map = objmap
                tmp.Stats = mMobs.colMobDB("MON" & CStr(spawn("ID_MON")))
                mSpawns.Add(tmp)

            Next

        End If

    End Sub

    Private Function GetAliveCount(ByVal spawnid As Int64) As Integer

        Dim c As Integer

        SyncLock objmap.objMapLock

            For Each mob As MirMapMob In mColMobs

                If mob.ServerSpawnID = spawnid And mob.IsAlive Then
                    c += 1
                End If

            Next

        End SyncLock

        Return c

    End Function

    Public Function DoSpawn() As Long

        Dim alive As Integer
        Dim tmp As MirMapMob
        Dim spawnedcount As Long = 0
        Dim tries As Int32

        SyncLock objmap.objMapLock

            For Each spawn As MobSpawn In mSpawns

                alive = GetAliveCount(spawn.SpawnID)

                If spawn.INT2_COUNT > alive Then

                    For i As Integer = 1 To spawn.INT2_COUNT - alive

                        tmp = New MirMapMob
                        tmp.ServerSpawnID = spawn.SpawnID
                        tmp.ServerMobID = GenerateMobID()
                        tmp.ID_MOB = spawn.ID_MON
                        tmp.stats = spawn.Stats
                        tmp.IsAlive = True
                        tries = 0
                        Do
                            Do
                                tmp.Pos.INT2_X = spawn.INT2_X + (Int(Rnd() * (spawn.INT2_RANGEX * 2)) - spawn.INT2_RANGEX)
                                tmp.Pos.INT2_Y = spawn.INT2_Y + (Int(Rnd() * (spawn.INT2_RANGEY * 2)) - spawn.INT2_RANGEY)
                                tries += 1
                                If tries > 16 Then Exit Do
                            Loop Until (tmp.Pos.INT2_X > 0 And tmp.Pos.INT2_X < spawn.map.Width And tmp.Pos.INT2_Y > 0 And tmp.Pos.INT2_Y < spawn.map.Height)
                            If tries > 16 Then Exit Do
                        Loop Until spawn.map.isPassable(tmp.Pos.INT2_X, tmp.Pos.INT2_Y) Or tries = 16

                        tmp.Pos.BYTE_DIRECTION = Int(Rnd() * 8)

                        tmp.Pos.INT8_SPEED = 2500000

                        tmp.map = spawn.map

                        tmp.VitalPoints.HP = CInt(spawn.Stats.Stats.INT2_MAX_HP)
                        tmp.VitalPoints.MP = CInt(spawn.Stats.Stats.INT2_MAX_MP)
                        tmp.VitalPoints.MAXHP = tmp.VitalPoints.HP
                        tmp.VitalPoints.MAXMP = tmp.VitalPoints.MP
                        mColMobs.Add(tmp)
                        MobAddToPlayers(tmp)

                        spawnedcount += 1

                    Next

                End If

            Next

        End SyncLock

        Return spawnedcount

    End Function

    Public Sub DoAI(ByVal tc As Long)

rescan:

        If mColMobs.Count = 0 Then Exit Sub

        Dim c As Integer = mColMobs.Count - 1
        Dim mob As MirMapMob

        Dim forceticks As Long = tc - 150000000

        For i As Integer = 0 To c

            SyncLock mColMobs(i).map.objMapLock

                mob = mColMobs(i)


                'If mob.map.IsVisibleToPlayer(mob.Pos.INT2_X, mob.Pos.INT2_Y) Or mob.AITimer < forceticks Then

                If mob.IsAlive And mob.VitalPoints.HP <= 0 Then

                    mob.IsAlive = False
                    mob.Pos.BYTE_ANIM = MobAnimation.Die

                    mob.AITimer = tc + 300000000
                    mob.AIState = -1

                    MobUpdatePlayers(mob)

                ElseIf mob.DoHitNextTick Then

                    mob.DoHitNextTick = False

                    mob.Pos.BYTE_ANIM = MobAnimation.TakingHit

                    With mob.stats
                        mob.AITimer = tc + (.AttackSpeed / 3)
                    End With

                    MobUpdatePlayers(mob)

                Else


                    If mob.AITimer <= tc Then

                        If mob.AIState = -1 Then

                            mob.map.Mobs.RemoveMob(mob)
                            MobRemove(mob)

                            GoTo rescan
                        Else

                            If mob.AITimer = 0 Then
                                mob.AITimer = tc
                                mob.AIHealTimer = tc + mob.map.Mobs.GetSpawn(mob.ServerSpawnID).Stats.HealSpeed
                            ElseIf mob.AIHealTimer <= tc Then
                                mob.AIHealTimer += mob.map.Mobs.GetSpawn(mob.ServerSpawnID).Stats.HealSpeed
                                If mob.VitalPoints.HP < mob.VitalPoints.MAXHP Then
                                    mob.VitalPoints.HP += mob.map.Mobs.GetSpawn(mob.ServerSpawnID).Stats.HealPoints
                                    If mob.VitalPoints.HP > mob.VitalPoints.MAXHP Then
                                        mob.VitalPoints.HP = mob.VitalPoints.MAXHP
                                    End If
                                    Dim pkt As Packet
                                    Dim struct As MobHitInfo

                                    pkt.PacketType = GamePacketType.MobHit
                                    pkt.PacketOpCode = PacketOpCode.Notify
                                    pkt.Username = DefaultUsername

                                    struct.ServerMobID = mob.ServerMobID
                                    struct.VitalPoints = mob.VitalPoints

                                    MobSendToPlayers(mob, pkt, struct)

                                End If
                            End If

                            MobAIs(colMobDB("MON" & mob.ID_MOB).INT2_AI).DoTick(mob, tc)

                        End If

                    End If

                End If


                mColMobs.RemoveAt(i)
                mColMobs.Insert(i, mob)

            End SyncLock

            'End If

        Next

    End Sub

    Public Function IsInRange(ByVal spawnid As Integer, ByVal x As Integer, ByVal y As Integer) As Boolean

        For Each spawn As MobSpawn In mSpawns
            If spawn.SpawnID = spawnid Then
                If x >= spawn.INT2_X - spawn.INT2_RANGEX And x <= spawn.INT2_X + spawn.INT2_RANGEX Then
                    If y >= spawn.INT2_Y - spawn.INT2_RANGEY And y <= spawn.INT2_Y + spawn.INT2_RANGEY Then
                        Return True
                    End If
                End If
                Return False
            End If
        Next

        Return True

    End Function

    Public Function GetSpawnDirection(mob As MirMapMob) As Direction

        For Each spawn As MobSpawn In mSpawns
            If spawn.SpawnID = mob.ServerSpawnID Then

                Dim dx As Integer, dy As Integer

                If mob.Pos.INT2_X < spawn.INT2_X Then
                    dx = 1
                ElseIf mob.Pos.INT2_X > spawn.INT2_X Then
                    dx = -1
                End If

                If mob.Pos.INT2_Y < spawn.INT2_Y Then
                    dy = 1
                ElseIf mob.Pos.INT2_Y > spawn.INT2_Y Then
                    dy = -1
                End If

                If dx = -1 Then
                    dx = RandNum(-1, 0)
                ElseIf dx = 1 Then
                    dx = RandNum(0, 1)
                End If

                If dy = -1 Then
                    dy = RandNum(-1, 0)
                ElseIf dy = 1 Then
                    dy = RandNum(0, 1)
                End If

                Do Until Not (dx = 0 And dy = 0)
                    dx = RandNum(-1, 1)
                    dy = RandNum(-1, 1)
                Loop

                Return GetDirFromOffsets(dx, dy)

            End If
        Next

        Return GetDirFromOffsets(RandNum(-1, 1), RandNum(-1, 1))

    End Function

    Friend Function GetMobAtPos(ByVal x As Integer, ByVal y As Integer) As MirMapMob

        SyncLock Me.objmap.objMapLock

            If mColMobs.Count = 0 Then Return Nothing

            For Each mob As MirMapMob In mColMobs

                If mob.IsAlive And mob.Pos.INT2_X = x And mob.Pos.INT2_Y = y Then
                    Return mob
                End If

            Next

            Return Nothing

        End SyncLock

    End Function

    Friend Function GetSpawn(ByVal spawnid As Int64) As MobSpawn

        For Each spawn As MobSpawn In mSpawns

            If spawn.SpawnID = spawnid Then
                Return spawn
            End If

        Next

        Return Nothing

    End Function

    Friend Sub SetMob(ByRef mob As MirMapMob)

        SyncLock objmap.objMapLock

            If mColMobs.Count = 0 Then Exit Sub

            Dim c As Integer = mColMobs.Count - 1

            For i As Integer = 0 To c

                If mColMobs(i).ServerMobID = mob.ServerMobID Then
                    mColMobs(i) = mob
                    Exit Sub
                End If

            Next

        End SyncLock

    End Sub

    Friend Sub RemoveMob(ByVal mob As MirMapMob)

        SyncLock objmap.objMapLock

            If mColMobs.Count = 0 Then Exit Sub

            Dim c As Integer = mColMobs.Count - 1

            For i As Integer = 0 To c

                If mColMobs(i).ServerMobID = mob.ServerMobID Then
                    mColMobs.RemoveAt(i)

                    Exit Sub
                End If

            Next

        End SyncLock

    End Sub

    Public Function GetMobFromIndex(ByVal idx As Int64) As MirMapMob

        SyncLock objmap.objMapLock

            If mColMobs.Count = 0 Then Return Nothing

            Dim c As Integer = mColMobs.Count - 1

            For i As Integer = 0 To c

                If mColMobs(i).ServerMobID = idx Then
                    Return mColMobs(i)
                End If

            Next

        End SyncLock

        Return Nothing

    End Function

    Public Sub MobSendChatMsg(ByRef conn As Connection, ByRef mob As MirMapMob, ByRef msg As String, ByRef type As ChatMsgType)

        Dim pkt As Packet
        pkt.PacketType = GamePacketType.MobChatMsg
        pkt.PacketOpCode = PacketOpCode.Notify
        pkt.Username = conn.Username

        Dim struct As MobChatMsg
        struct.ServerMobID = mob.ServerMobID
        struct.M.STR_MSG = msg
        struct.BYTE_TYPE = Type

        SendToLocalPlayers(conn, pkt, struct)

    End Sub

    Public Sub MobExistenceCheck(ByRef Conn As Connection, ByRef mobid As Int64)

        SyncLock objmap.objMapLock

            For Each M As MirMapMob In mColMobs

                If M.ServerMobID = mobid And M.VitalPoints.HP > 0 Then
                    Exit Sub
                End If

            Next

            Dim pkt As Packet
            pkt.PacketType = GamePacketType.MobRemoveUpdate
            pkt.PacketOpCode = PacketOpCode.Notify
            pkt.Username = Conn.Username

            Dim struct As MobRemoveInfo
            struct.ServerMobID = mobid

            Socket.SockServer.SendData(Protocol.MakePacket(pkt, struct, Conn.Crypto), Conn.Index)

        End SyncLock

    End Sub

    Public Sub ResyncMob(ByRef conn As Connection, ByRef mobid As Int64)

        SyncLock objmap.objMapLock

            For Each M As MirMapMob In mColMobs

                If M.ServerMobID = mobid Then

                    Dim addreply As Packet
                    addreply.PacketType = GamePacketType.MobAddUpdate
                    addreply.PacketOpCode = PacketOpCode.Notify
                    addreply.Username = conn.Username

                    Dim addstruct2 As MobAddInfo
                    addstruct2.ServerMobID = M.ServerMobID
                    addstruct2.INT2_SHAPE = mMobs.colMobDB("MON" & M.ID_MOB).INT2_SHAPE
                    addstruct2.Positioninfo = M.Pos
                    addstruct2.VitalPoints = M.VitalPoints
                    addstruct2.Info.Name = mMobs.colMobDB("MON" & M.ID_MOB).STR_NAME
                    addstruct2.Info.Level = mMobs.colMobDB("MON" & M.ID_MOB).INT2_LEVEL
                    addstruct2.Info.Avatar = mMobs.colMobDB("MON" & M.ID_MOB).AvatarImg


                    Socket.SockServer.SendData(Protocol.MakePacket(addreply, addstruct2, conn.Crypto), conn.Index)

                    Exit Sub

                End If
            Next

        End SyncLock

    End Sub

End Class